package com.chenfan.magic.core;

import com.chenfan.magic.annation.FetchHandle;
import com.chenfan.magic.base.R;
import com.chenfan.magic.constant.MagicConstant;
import com.chenfan.magic.constant.RuntimeConstant;
import com.chenfan.magic.core.executer._MainExecuter;
import com.chenfan.magic.service.IIsvService;
import com.chenfan.magic.entity.*;
import com.chenfan.magic.exception.MagicDebugException;
import com.chenfan.magic.exception.MagicException;
import com.chenfan.magic.resolver._MainResolver;
import com.chenfan.magic.service.IDatasourceService;
import com.chenfan.magic.service.IScriptsPublishService;
import com.chenfan.magic.service.IScriptsService;
import com.chenfan.magic.utils.Exceptions;
import com.chenfan.magic.utils.FastJsonKit;
import com.chenfan.magic.utils.IDKit;
import com.chenfan.magic.utils.StringKit;
import com.chenfan.magic.validator._MainValidator;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * IDC 引擎入口
 *
 * @author 周夕
 * @date 2020-02-26 19:00
 */
@Slf4j
@Component
@AllArgsConstructor
public class IdcEngineEntry {

    private IIsvService isvService;
    private IScriptsPublishService scriptsPublishService;
    private IScriptsService scriptsService;
    private IDatasourceService datasourceService;

    private _MainExecuter mainExecuter;

    /**
     * 统一执行入口
     *
     * @param
     * @param scriptIdOrIndexOrRoute
     * @param debug
     * @return
     */
    public R execute(String scriptIdOrIndexOrRoute, boolean debug, boolean paramSwitch, boolean isPure) {
        return entry(scriptIdOrIndexOrRoute, debug, paramSwitch, false, null, isPure);
    }

    @FetchHandle
    public R execute(String scriptIdOrIndexOrRoute, boolean debug, boolean isPure) {
        return execute(scriptIdOrIndexOrRoute, debug, false, isPure);
    }

    public R execute(String scriptIdOrIndexOrRoute) {
        return execute(scriptIdOrIndexOrRoute, false, false);
    }

    /**
     * 中间通道
     *
     * @param
     * @param scriptIdOrIndexOrRoute
     * @param debug
     * @param paramSwitch
     * @return
     */
    public R entry(String scriptIdOrIndexOrRoute, boolean debug, boolean paramSwitch, boolean isInFilter, String typeCodeSpix, boolean isPure) {
        MemoryModel memoryModel = new MemoryModel();
        memoryModel.start();
        TTLModel ttlModel = new TTLModel();
        TTLDetailModel ttlDetailModel = new TTLDetailModel();
        ttlDetailModel.start("magic#bus");
        R result = R.success();
        Map<String, Object> idcRuntimePool = Maps.newHashMap();
        idcRuntimePool.put(RuntimeConstant.RUNTIME_IS_PURE, isPure);
        pretreat(scriptIdOrIndexOrRoute, ttlModel, idcRuntimePool, debug, paramSwitch);
        Isv isv = (Isv) idcRuntimePool.get(RuntimeConstant.RUNTIME_ISV);


        if (debug) {
            IdcScripts script = (IdcScripts) idcRuntimePool.get(RuntimeConstant.RUNTIME_SCRIPT);
            result = debug(idcRuntimePool, ttlModel, memoryModel, isv, script, paramSwitch);
        } else {
            IdcScriptsPublish script = (IdcScriptsPublish) idcRuntimePool.get(RuntimeConstant.RUNTIME_SCRIPT);
            result = run(idcRuntimePool, ttlModel, null, isv, script);
        }

        Map<String, Object> globalFilters = (Map<String, Object>) idcRuntimePool.get(RuntimeConstant.RUNTIME_FILTERS_GLOBAL);
        globalFilters = globalFilters == null ? Maps.newHashMap() : globalFilters;
        Map<String, Object> partFilters = (Map<String, Object>) idcRuntimePool.get(RuntimeConstant.RUNTIME_FILTERS_PART);
        partFilters = partFilters == null ? Maps.newHashMap() : partFilters;
        if (isInFilter && StringKit.isNotBlank(typeCodeSpix)) {
            if (MagicConstant.TYPE_CODE_SPIX_G.equals(typeCodeSpix)) {
                globalFilters.put("[" + typeCodeSpix + "]" + scriptIdOrIndexOrRoute, result);
            } else if (MagicConstant.TYPE_CODE_SPIX_P.equals(typeCodeSpix)) {
                partFilters.put("[" + typeCodeSpix + "]" + scriptIdOrIndexOrRoute, result);
            }
        }
        //重新计算时间
        ttlModel.getExecuter().caculate().setExplain("调度器包含了除一级jsonp解析器外的所有解析器时间.");
        ttlDetailModel.end();
        ttlModel.setUsed(ttlDetailModel.getTtl());
        if (debug) {
            result.put("#ttl", ttlDetailModel.getTtl());
        }
        return result;
    }

    /**
     * 正式线路 [prod]
     *
     * @return
     */
    public R run(Map<String, Object> idcRuntimePool, TTLModel ttlModel, MemoryModel memoryModel, Isv isv, IdcScriptsPublish script) {
        Map<String, Object> paramMap = new _MainValidator().validate(ttlModel, idcRuntimePool, isv, script.getSpScriptPreRequestParams(), script.getSpScriptTokenArrows());
        takeRuntimeVar(idcRuntimePool, datasourceService.get(isv), RuntimeConstant.RUNTIME_DATASOURCES);
        MagicLogPublisher.publishExecuterEvent(idcRuntimePool.get(RuntimeConstant.RUNTIME_ID) + "", 0, 1, script.getSpScriptId(), script.getSpScriptPublishVersion(), script.getSpScriptContext(), "");
        new _MainResolver().resolve(idcRuntimePool, ttlModel);
        R result = mainExecuter.run(script.getSpScriptIndex(), idcRuntimePool, paramMap, ttlModel);
        return result;
    }

    /**
     * 开发线路 [debug]
     *
     * @param paramSwitch 调试模式下, 用配参还是用入参. 为 true 代表用配参.
     * @return
     */
    public R debug(Map<String, Object> idcRuntimePool, TTLModel ttlModel, MemoryModel memoryModel, Isv isv, IdcScripts script, boolean paramSwitch) {
        Map<String, Object> paramMap = new _MainValidator().validate(true, paramSwitch, ttlModel, idcRuntimePool, isv, script.getPreRequestParams(), script.getTokenArrows());
        takeRuntimeVar(idcRuntimePool, datasourceService.get(isv), RuntimeConstant.RUNTIME_DATASOURCES);
        MagicLogPublisher.publishExecuterEvent(idcRuntimePool.get(RuntimeConstant.RUNTIME_ID) + "", 0, 0, script.getId(), script.getPublishVersion(), script.getContext(), "");
        new _MainResolver().resolve(idcRuntimePool, ttlModel);
        R result = mainExecuter.run(script.getIndez(), idcRuntimePool, paramMap, ttlModel, memoryModel, true);
        return result;
    }

    public R debug(Map<String, Object> idcRuntimePool, TTLModel ttlModel, MemoryModel memoryModel, Isv isv, IdcScripts script) {
        return debug(idcRuntimePool, ttlModel, memoryModel, isv, script, false);
    }

    /**
     * 局部解析 [debug]
     *
     * @param scriptIdOrIndexOrRoute MQL - id/index/route
     * @param paramSwitch            调试模式下, 用配参还是用入参. 为 true 代表用配参.
     * @return
     */
    public R resolve(String scriptIdOrIndexOrRoute, String context, boolean paramSwitch) {
        TTLModel ttlModel = new TTLModel();
        Map<String, Object> idcRuntimePool = Maps.newHashMap();
        pretreat(scriptIdOrIndexOrRoute, ttlModel, idcRuntimePool, true, paramSwitch);
        Isv isv = (Isv) idcRuntimePool.get(RuntimeConstant.RUNTIME_ISV);
        IdcScripts script = (IdcScripts) idcRuntimePool.get(RuntimeConstant.RUNTIME_SCRIPT);

        Map<String, Object> paramMap = new _MainValidator().validate(true, paramSwitch, ttlModel, idcRuntimePool, isv, script.getPreRequestParams(), script.getTokenArrows());
        takeRuntimeVar(idcRuntimePool, datasourceService.get(isv), RuntimeConstant.RUNTIME_DATASOURCES);
        return new _MainResolver().resolvePart(ttlModel, idcRuntimePool, context, paramMap);
    }

    /**
     * 预处理
     *
     * @param scriptIdOrIndexOrRoute
     * @param debug
     * @return
     */
    public void pretreat(String scriptIdOrIndexOrRoute, TTLModel ttlModel, Map<String, Object> idcRuntimePool, boolean debug, boolean paramSwitch) {
        TTLDetailModel ttlDetailModel = new TTLDetailModel();
        ttlDetailModel.start();

        String runtimeId = IDKit.fastSimpleUUID();
        idcRuntimePool.put(RuntimeConstant.RUNTIME_ID, runtimeId);

        Isv isv = null;
        String index = null, context = null, resolveEngine = null, isFilter = null;
        if (debug) {
            IdcScripts script = (IdcScripts) takeRuntimeVar(idcRuntimePool, scriptsService.get(scriptIdOrIndexOrRoute), RuntimeConstant.RUNTIME_SCRIPT);
            isv = (Isv) takeRuntimeVar(idcRuntimePool, isvService.getInDebug(script.getAppId()), RuntimeConstant.RUNTIME_ISV);
            index = script.getIndez();
            context = script.getContextDecrypt(isv.getAppSecret());
            resolveEngine = script.getResolveEngine();
            isFilter = script.getIsFilter() + "";
        } else {
            IdcScriptsPublish script = (IdcScriptsPublish) takeRuntimeVar(idcRuntimePool, scriptsPublishService.get(scriptIdOrIndexOrRoute), RuntimeConstant.RUNTIME_SCRIPT);
            isv = (Isv) takeRuntimeVar(idcRuntimePool, isvService.getInProd(script.getSpScriptAppId()), RuntimeConstant.RUNTIME_ISV);
            index = script.getSpScriptIndex();
            context = script.getSpScriptContextDecrypt(isv.getAppSecret());
            resolveEngine = script.getSpScriptResolveEngine();
            isFilter = script.getSpScriptIsFilter() + "";
        }
        idcRuntimePool.put(RuntimeConstant.RUNTIME_DEBUG, debug);
        idcRuntimePool.put(RuntimeConstant.RUNTIME_SWITCH_PARAM, paramSwitch);
        idcRuntimePool.put(RuntimeConstant.RUNTIME_IS_FILTER, isFilter);
        idcRuntimePool.put(RuntimeConstant.RUNTIME_ORIGN_SCRIPT_INDEX, index);
        idcRuntimePool.put(RuntimeConstant.RUNTIME_ORIGN_SCRIPT_CONTEXT, context);
        idcRuntimePool.put(RuntimeConstant.RUNTIME_ORIGN_SCRIPT_ENGINE, resolveEngine);
        idcRuntimePool.put(RuntimeConstant.RUNTIME_ISV_WEBHOOKS, isv.getAppRobotDings());
        idcRuntimePool.put(RuntimeConstant.RUNTIME_ISV_ENGINE, isv.getAppResolveEngine());

        ttlDetailModel.end();
        ttlModel.setPretreat(ttlDetailModel);

    }


    /**
     * 推送引擎执行异常
     *
     * @param finalResult
     * @param msg
     */
    public static void throwException(Map<String, Object> idcRuntimePool, Map<String, Object> finalResult, String msg, Integer code, Exception e) {
        String stacktrace = "";
        if (e != null) {
            stacktrace = Exceptions.getStackTraceAsString(e);
        }
        Boolean debug = Boolean.parseBoolean(idcRuntimePool.get(RuntimeConstant.RUNTIME_DEBUG) + "");
        Map<String, Object> errorMessageDataMap = Maps.newHashMap();
        errorMessageDataMap.put("executeId", idcRuntimePool.get(RuntimeConstant.RUNTIME_ID));
        errorMessageDataMap.put("isvWebhooks", idcRuntimePool.get(RuntimeConstant.RUNTIME_ISV_WEBHOOKS));
        errorMessageDataMap.put("resultScope", FastJsonKit.map2Jsonstring(finalResult));
        errorMessageDataMap.put("resultRes", FastJsonKit.map2Jsonstring((Map<String, Object>) idcRuntimePool.get(RuntimeConstant.RUNTIME_RESULTSET)));
        errorMessageDataMap.put("resultDesc", msg + "\n" + stacktrace);
        if (debug) {
            throw new MagicDebugException(msg + "\n" + stacktrace, (code != null ? code : 7000000), errorMessageDataMap);
        } else {
            throw new MagicException(msg, (code != null ? code : 7000000), errorMessageDataMap);
        }
    }

    public static void throwException(Map<String, Object> idcRuntimePool, Map<String, Object> finalResult, Exception e) {
        throwException(idcRuntimePool, finalResult, "接口执行异常", null, e);
    }

    public static void throwException(Map<String, Object> idcRuntimePool, Map<String, Object> finalResult, String msg) {
        throwException(idcRuntimePool, finalResult, msg, null, null);
    }

    public static void throwException(Map<String, Object> idcRuntimePool, Map<String, Object> finalResult, String msg, Exception e) {
        throwException(idcRuntimePool, finalResult, msg, null, e);
    }

    public static void throwException(Map<String, Object> idcRuntimePool, Map<String, Object> finalResult, Map<String, Object> eofMap) {
        throwException(idcRuntimePool, finalResult, (eofMap.get("msg") + ""), Integer.parseInt(eofMap.get("code") + ""), null);
    }


    /**
     * 缓存运行时变量
     *
     * @param r
     * @param runtimeDataKey
     */
    public static Object takeRuntimeVar(Map<String, Object> idcRuntimePool, R r, String runtimeDataKey) {
        if (r.takeStatus()) {
            Object obj = r.get();
            if (obj != null) {
                idcRuntimePool.put(runtimeDataKey, obj);
                return obj;
            }
        } else {
            throwException(idcRuntimePool, null, r.getMsg());
        }
        return null;
    }

    public static Object takeRuntimeVar(Map<String, Object> idcRuntimePool, R r) {
        if (r.takeStatus()) {
            idcRuntimePool.putAll(r.getData());
            return r.getData();
        } else {
            throwException(idcRuntimePool, null, r.getMsg());
        }
        return null;
    }

}
